/**
 @file ctc_phy_drv.c
 @copyright Copyright(C)  Centec Networks Inc.  All rights reserved.
 @date 2019-10-23
 @version v1.0
*/

#if (SDK_WORK_PLATFORM == 0)
#include "sal.h"
#include "ctc_phy_inc.h"
#include "phy_drv_common.h"
#include "phy_drv_api.h"

#ifdef CTC_PHY_DRV_PLUGIN
#include <dlfcn.h>
#include <dirent.h>
#define CTC_PHY_DRIVER_DIR "/usr/lib/phy_drv"
#endif

extern uint8 g_ctcs_api_en;

extern phy_manufacturers_t phy_manuf_ids[];

extern phy_info_t g_phy_port_info[CTC_MAX_PHY_PORT_NUM];

#ifdef ASW_ALONE
ctc_phy_port_t phy_ax_demo_port_max[24] =
{
    { 0, 0,  0, 0 ,},
    { 1, 0,  1, 0 ,},
    { 2, 0,  2, 0 ,},
    { 3, 0,  3, 0 ,},

    { 4, 0,  4, 0 ,},
    { 5, 0,  5, 0 ,},
    { 6, 0,  6, 0 ,},
    { 7, 0,  7, 0 ,},

    { 8, 1,  0, 8 ,},
    { 9, 1,  1, 8 ,},
    {10, 1, 2, 8 ,},
    {11, 1, 3, 8 ,},

    {12, 1, 4, 8 ,},
    {13, 1, 5, 8 ,},
    {14, 1, 6, 8 ,},
    {15, 1, 7, 8 ,},

    {16, 1,  8, 16 ,},
    {17, 1,  9, 16 ,},
    {18, 1,  10, 16 ,},
    {19, 1,  11, 16 ,},

    {20, 1,  12, 16 ,},
    {21, 1,  13, 16 ,},
    {22, 1,  14, 16 ,},
    {23, 1,  15, 16 ,},
};

/**
@fn int32 ctc_app_set_phy_mdio_mapping(void)
@ingroup SDK_API
@details This function will do map mdio bus and mdio addr to mac port according to mdio mapping file.
        If the mapping file is not exist, mdio mapping fail and return.
        If the mapping file is exist, you can "show phy-port map" after CLI register to see the actual mapping.
        The mdio mapping file need exist in dir where the main program exist.
        Default "./phy_mapping.cfg".
@return alway 0.        
@note The mdio mapping file has specific format. Here is an example as below:
@code
#{
[PHY_MAPPING_ITEM]
#repeat 8 step 3
[API_PORT]  = 5
[PHY_ADDR] = 1
[MDIO_BUS]  = 0

[API_PORT]  = 4
[PHY_ADDR] = 0
[MDIO_BUS]  = 0

#repeat end
#}
@endcode
*/
int32
ctc_app_set_phy_mdio_mapping(void)
{
    int32 ret = 0;
    int32 index = 0;

    for (index = 0; index < sizeof(phy_ax_demo_port_max)/sizeof(ctc_phy_port_t); index++)
    {
            //lport = CTC_MAP_GPORT_TO_LPORT(tmp_gport[index]);
            g_phy_port_info[index].port_id= phy_ax_demo_port_max[index].port_id;
            g_phy_port_info[index].phy_addr = phy_ax_demo_port_max[index].phy_addr;
            g_phy_port_info[index].bus = phy_ax_demo_port_max[index].bus;
            g_phy_port_info[index].phy_base_port = phy_ax_demo_port_max[index].phy_base_port;
            g_phy_port_info[index].phy_last_port = phy_ax_demo_port_max[index].phy_last_port;
    }

    return ret;
}

/**
@fn int32 ctc_app_get_phy_mdio_en(uint8 *enable)
@ingroup SDK_API
@details This function will get phy mapping enable from the chip config file.
        Default chip config file is "./chip_profile.cfg".
@param enable value for phy mapping enable or not.          
@return alway 0.        
*/
int32
ctc_app_get_phy_mdio_en(uint8 lchip, uint8 *enable)
{
    *enable = 1;
    return 0;
}

static inline int32 cal_set_phy_prop(uint8 lchip, uint32 gport, ctc_port_property_t port_prop, uint32* value)
{
    return ctcs_port_set_phy_property(0, gport, CTC_PORT_PROP_PHY_CUSTOM_MAX_TYPE, value);
}

static inline int32 cal_get_lchip_num(uint8 lchip, uint8* lchip_num)
{
    *lchip_num = 1;
    return 0;
}

static inline int32 cal_set_port_prop(uint8 lchip, uint32 gport, ctc_port_property_t port_prop, uint32 value)
{
    return ctcs_port_set_property(0, gport, port_prop, value);
}

#else

#if defined(_SAL_LINUX_KM)
int file_exist(char *filename)
{
    struct file *fp = NULL;
    fp = filp_open(filename, O_RDWR, 0);
    if ((NULL == fp) || (IS_ERR(fp)))
    {
        return 0;
    }
    filp_close(fp, NULL);
    return 1;
}
#else
int file_exist(char *filename)
{
    struct stat stat_buf;
    if (! stat(filename, &stat_buf))
    {
        return 1;
    }
    return 0;
}

static inline int32 cal_get_lchip_num(uint8 lchip, uint8* lchip_num)
{
    int32 ret = 0;
    uint8 chip_num = 0;
    if (g_ctcs_api_en)
    {
        ret = ctcs_get_local_chip_num(lchip, &chip_num);
    }
    else
    {
        ret = ctc_get_local_chip_num(&chip_num);
    }
    *lchip_num = chip_num;
#if defined(TSINGMA_AX)
    if (!chip_num)
    {
        *lchip_num += 1;
    }
#endif
    return ret;
}

static inline int32 cal_set_phy_prop(uint8 lchip, uint32 gport, ctc_port_property_t port_prop, uint32* value)
{
    if(g_ctcs_api_en)
    {
        return ctcs_port_set_phy_property(lchip, gport, port_prop, value);
    }
    else
    {
        return ctc_port_set_phy_property(gport, port_prop, value);
    }
}

static inline int32 cal_set_port_prop(uint8 lchip, uint32 gport, ctc_port_property_t port_prop, uint32 value)
{
    if(g_ctcs_api_en)
    {
        return ctcs_port_set_property(lchip, gport, port_prop, value);
    }
    else
    {
        return ctc_port_set_property(gport, port_prop, value);
    }
}

#endif

/**
@fn int32 ctc_app_set_phy_mdio_mapping(void)
@ingroup SDK_API
@details This function will do map mdio bus and mdio addr to mac port according to mdio mapping file.
        If the mapping file is not exist, mdio mapping fail and return.
        If the mapping file is exist, you can "show phy-port map" after CLI register to see the actual mapping.
        The mdio mapping file need exist in dir where the main program exist.
        Default "./phy_mapping.cfg".
@return alway 0.        
@note The mdio mapping file has specific format. Here is an example as below:
@code
#{
[PHY_MAPPING_ITEM]
#repeat 8 step 3
[API_PORT]  = 5
[PHY_ADDR] = 1
[MDIO_BUS]  = 0

[API_PORT]  = 4
[PHY_ADDR] = 0
[MDIO_BUS]  = 0

#repeat end
#}
@endcode
*/
int32
ctc_app_set_phy_mdio_mapping(void)
{
    int32 ret;
    ctc_app_parse_file_t file;
    uint16 entry_num = 0;
    uint32* tmp_gport = NULL;
    uint32* tmp_phy = NULL;
    uint32* tmp_mdio = NULL;
    uint32* tmp_base_port = NULL;
    uint32* tmp_last_port = NULL;
    uint32* tmp_lchip = NULL;

    uint32 index = 0;
    //uint16 lport = 0;

    if(!file_exist(PHY_PORT_MAPPING_FILE))
    {
        return -1;
    }

    tmp_gport = (uint32*)sal_malloc(sizeof(uint32)*CTC_MAX_PHY_PORT_NUM);
    tmp_phy = (uint32*)sal_malloc(sizeof(uint32)*CTC_MAX_PHY_PORT_NUM);
    tmp_mdio = (uint32*)sal_malloc(sizeof(uint32)*CTC_MAX_PHY_PORT_NUM);
    tmp_base_port = (uint32*)sal_malloc(sizeof(uint32)*CTC_MAX_PHY_PORT_NUM);
    tmp_last_port = (uint32*)sal_malloc(sizeof(uint32)*CTC_MAX_PHY_PORT_NUM);
    tmp_lchip = (uint32*)sal_malloc(sizeof(uint32)*CTC_MAX_PHY_PORT_NUM);
    sal_memset(tmp_gport, CTC_MAX_UINT32_VALUE, sizeof(uint32)*CTC_MAX_PHY_PORT_NUM);
    sal_memset(tmp_phy, CTC_MAX_UINT32_VALUE, sizeof(uint32)*CTC_MAX_PHY_PORT_NUM);
    sal_memset(tmp_mdio, CTC_MAX_UINT32_VALUE, sizeof(uint32)*CTC_MAX_PHY_PORT_NUM);
    sal_memset(tmp_base_port, CTC_MAX_UINT32_VALUE, sizeof(uint32)*CTC_MAX_PHY_PORT_NUM);
    sal_memset(tmp_last_port, CTC_MAX_UINT32_VALUE, sizeof(uint32)*CTC_MAX_PHY_PORT_NUM);
    sal_memset(tmp_lchip, CTC_MAX_UINT32_VALUE, sizeof(uint32)*CTC_MAX_PHY_PORT_NUM);

    ret = ctc_app_parse_open_file(PHY_PORT_MAPPING_FILE, &file);
    if(ret != CTC_E_NONE)
    {
        goto error;
    }

    entry_num = CTC_MAX_PHY_PORT_NUM;
    ret = ctc_app_parse_file(&file, "PHY_MAPPING_ITEM", "API_PORT", tmp_gport, &entry_num);
    ctc_app_parse_close_file(&file);

    ret = ctc_app_parse_open_file(PHY_PORT_MAPPING_FILE, &file);
    if(ret != CTC_E_NONE)
    {
        goto error;
    }
    entry_num = CTC_MAX_PHY_PORT_NUM;
    ret = ctc_app_parse_file(&file, "PHY_MAPPING_ITEM", "PHY_ADDR", tmp_phy, &entry_num);
    ctc_app_parse_close_file(&file);

    ret = ctc_app_parse_open_file(PHY_PORT_MAPPING_FILE, &file);
    if(ret != CTC_E_NONE)
    {
        goto error;
    }
    entry_num = CTC_MAX_PHY_PORT_NUM;
    ret = ctc_app_parse_file(&file, "PHY_MAPPING_ITEM", "MDIO_BUS", tmp_mdio, &entry_num);
    ctc_app_parse_close_file(&file);

    ret = ctc_app_parse_open_file(PHY_PORT_MAPPING_FILE, &file);
    if(ret != CTC_E_NONE)
    {
        goto error;
    }
    entry_num = CTC_MAX_PHY_PORT_NUM;
    ctc_app_parse_file(&file, "PHY_MAPPING_ITEM", "PHY_BASE", tmp_base_port, &entry_num);
    ctc_app_parse_close_file(&file);

    ret = ctc_app_parse_open_file(PHY_PORT_MAPPING_FILE, &file);
    if(ret != CTC_E_NONE)
    {
        goto error;
    }
    entry_num = CTC_MAX_PHY_PORT_NUM;
    ctc_app_parse_file(&file, "PHY_MAPPING_ITEM", "PHY_END", tmp_last_port, &entry_num);
    ctc_app_parse_close_file(&file);

    ret = ctc_app_parse_open_file(PHY_PORT_MAPPING_FILE, &file);
    if(ret != CTC_E_NONE)
    {
        goto error;
    }
    entry_num = CTC_MAX_PHY_PORT_NUM;
    ctc_app_parse_file(&file, "PHY_MAPPING_ITEM", "PHY_LCHIP", tmp_lchip, &entry_num);
    ctc_app_parse_close_file(&file);

    for (index = 0; index < CTC_MAX_PHY_PORT_NUM; index++)
    {
        if (CTC_MAX_UINT32_VALUE != tmp_gport[index])
        {
            //lport = CTC_MAP_GPORT_TO_LPORT(tmp_gport[index]);
            g_phy_port_info[index].port_id= tmp_gport[index];
            g_phy_port_info[index].phy_addr = tmp_phy[index];
            g_phy_port_info[index].bus = tmp_mdio[index];
            g_phy_port_info[index].phy_base_port = (tmp_base_port[index] == 0xffffffff)?0:tmp_base_port[index];
            g_phy_port_info[index].phy_last_port = (tmp_last_port[index] == 0xffffffff)?0:tmp_last_port[index];
            g_phy_port_info[index].lchip = (tmp_lchip[index] == 0xffffffff)?0:tmp_lchip[index];
        }
    }

error:
    if (tmp_gport)
    {
        sal_free(tmp_gport);
    }
    if (tmp_phy)
    {
        sal_free(tmp_phy);
    }
    if (tmp_mdio)
    {
        sal_free(tmp_mdio);
    }
    if (tmp_base_port)
    {
        sal_free(tmp_base_port);
    }
    if (tmp_last_port)
    {
        sal_free(tmp_last_port);
    }
    if (tmp_lchip)
    {
        sal_free(tmp_lchip);
    }

    return ret;
}

/**
@fn int32 ctc_app_get_phy_mdio_en(uint8 *enable)
@ingroup SDK_API
@details This function will get phy mapping enable from the chip config file.
        Default chip config file is "./chip_profile.cfg".
@param enable value for phy mapping enable or not.          
@return alway 0.        
*/
int32
ctc_app_get_phy_mdio_en(uint8 lchip, uint8 *enable)
{
    int32 ret;
    ctc_app_parse_file_t file;
    uint16 entry_num = 0;
    uint32 val = 0;
    uint8 json_parse_en = 0;
    cJSON* pJson = NULL;
    cJSON* p_json = NULL;
    cJSON* p_tmp_json = NULL;

    *enable = 0;
    if(file_exist(PHY_JSON_CONFIG))
    {
        json_parse_en = 1;
    }
    if (json_parse_en)
    {
        ret = ctc_app_parse_open_file(PHY_JSON_CONFIG, &file);
        if(ret != CTC_E_NONE)
        {
            return -1;
        }
        if(ctc_json_parse((const char *)file.mem_addr, &pJson))
        {
            ctc_json_free(pJson);
            return -1;
        }
        p_json = pJson;
        p_tmp_json = ctc_json_get_object(p_json, "port_phy_mapping_en");
        if(p_tmp_json && p_tmp_json->type == APP_JSON_TYPE_NUMBER)
            *enable = (uint8)p_tmp_json->value.number;
        
        ctc_json_free(p_json);
        ctc_app_parse_close_file(&file);
    }
    else
    {
        if(!file_exist(PHY_CHIP_CONFIG))
        {
            return -1;
        }

        ret = ctc_app_parse_open_file(PHY_CHIP_CONFIG, &file);
        if(ret != CTC_E_NONE)
        {
            return -1;
        }
        
        entry_num = 1;
        if (!ctc_app_parse_file(&file, "Port_phy_mapping", NULL, &val, &entry_num))
        {
            *enable = (uint8)val;
        }
        ctc_app_parse_close_file(&file);
    }

    return 0;
}
#endif

/**
@fn int32 ctc_app_phy_register(uint8 lchip)
@ingroup SDK_API
@details This function will register phy driver to system. All phy driver in table phy_manuf_ids will register to system.
@param lchip asic chip id        
@return alway 0.
*/
#ifndef CTC_PHY_DRV_PLUGIN
int32
ctc_app_phy_register(uint8 lchip)
{
    int8 i = 0;
    int32 ret = CTC_E_NONE;
    phy_manufacturers_t *phy;
    ctc_chip_phy_shim_t phy_shim;
    ctc_phy_driver_t *phy_drv=NULL;
    for(i = 0;i<ctc_phy_driver_num();i++)
    {
        phy = &phy_manuf_ids[i];
        if(phy->id !=PHY_TYPE_UNKNOWN && phy->driver_get != NULL)
        {
            phy->driver_get((void*)&phy_drv);
            if(phy_drv == NULL)
            {
                continue;
            }
            sal_memset(&phy_shim, 0, sizeof(ctc_chip_phy_shim_t));
            phy_shim.phy_id = phy->id;
            phy_shim.phy_id_mask = phy->id_mask;
            sal_memcpy(phy_shim.phy_name, phy->name, CTC_CHIP_PHY_NAME_MAX_LEN);    
            sal_memcpy(&phy_shim.driver, phy_drv, sizeof(ctc_phy_driver_t));
            phy_shim.event_mode = CTC_CHIP_PHY_SHIM_EVENT_MODE_POLLING;
            phy_shim.poll_interval = CTC_CHIP_PHY_POLL_INTERVAL;/* ms */
            ret += cal_set_chip_prop(lchip, CTC_CHIP_PROP_REGISTER_PHY, (void*)&phy_shim);
        }
    }
    return ret;
}
#else
int32
ctc_app_phy_register(uint8 lchip)
{
    DIR *pdir = NULL;
    struct dirent *pent = NULL;
    void (*ctc_app_phy_driver_init)(uint8) = NULL;
    void *handle = NULL;
    char dl_libname[256] = "";
    char *error = NULL;
    char tmp[256] = "";

    strcpy(tmp, CTC_PHY_DRIVER_DIR);
    strcat(tmp, "/");

    pdir = opendir(tmp);
    if (pdir == NULL)
    {
        return 0;
    }

    while ((pent = readdir(pdir)) != NULL)
    {
        if (pent->d_name[0] == '.')
        {
            handle = NULL;
            ctc_app_phy_driver_init = NULL;
            continue;
        }

        strcpy(dl_libname, tmp);
        strcat(dl_libname, pent->d_name);
        handle = dlopen(dl_libname, RTLD_LAZY | RTLD_GLOBAL);
        if (handle == NULL)
        {
            handle = NULL;
            ctc_app_phy_driver_init = NULL;
            continue;
        }

        ctc_app_phy_driver_init = (void (*)(uint8))dlsym(handle, "ctc_app_phy_driver_init");
        if ((error = dlerror()) != NULL)
        {
            handle = NULL;
            ctc_app_phy_driver_init = NULL;
            continue;
        }

        ctc_app_phy_driver_init(lchip);
        handle = NULL;
        ctc_app_phy_driver_init = NULL;
    }

    closedir(pdir);

    return 0;
}
#endif

/**
@fn int32 ctc_app_phy_info_init(uint8 lchip)
@ingroup SDK_API
@details This function will call ctc_app_set_phy_mdio_mapping for mdio mapping and do g_phy_port_info initialize.
@return alway 0.
*/
int32
ctc_app_phy_info_init(void)
{
    uint32 i = 0;
    int32 ret = CTC_E_NONE;
    sal_memset(g_phy_port_info, 0, CTC_MAX_PHY_PORT_NUM * sizeof(phy_info_t));
    /* for init mutex */
    ctc_phy_sem_take(0, 0);
    ctc_phy_sem_give(0);
    for(i = 0;i<CTC_MAX_PHY_PORT_NUM;i++)
    {
        g_phy_port_info[i].port_id = PHY_INVALID_ID;
        g_phy_port_info[i].phy_id = PHY_TYPE_UNKNOWN;
        g_phy_port_info[i].bus = 0xff;
        g_phy_port_info[i].sem_take = ctc_phy_sem_take;
        g_phy_port_info[i].sem_give = ctc_phy_sem_give;
    }
    ret += ctc_app_set_phy_mdio_mapping();
    return ret;
}

/**
@fn int32 ctc_app_phy_device_pre_init(uint8 lchip)
@ingroup SDK_API
@details This function do some pre-operation before device initialize base on phy driver define.
@return error code.
*/
int32
ctc_app_phy_device_pre_init(uint8 lchip)
{
    int32 ret = 0;
    uint32 value = 1;
    uint32 loop = 0;
    uint32 gport = 0;
    for (loop = 0; loop < CTC_MAX_PHY_PORT_NUM; loop++)
    {
        if(g_phy_port_info[loop].bus == 0xff)
        {
            continue;
        }
        gport = g_phy_port_info[loop].port_id;
        ret = cal_set_phy_prop(g_phy_port_info[loop].lchip, gport, CTC_PORT_PROP_PHY_CUSTOM_BASE+PHY_CUSTOM_PREINIT, &value);
    }
    return ret==CTC_E_NOT_SUPPORT?CTC_E_NONE:ret;
}

/**
@fn int32 ctc_app_phy_device_post_init(uint8 lchip)
@ingroup SDK_API
@details This function do some post-operation after device initialize base on phy driver define.
@return error code.
*/
int32
ctc_app_phy_device_post_init(uint8 lchip)
{
    int32 ret = 0;
    uint32 value = 1;
    uint32 loop = 0;
    uint32 gport = 0;
    for (loop = 0; loop < CTC_MAX_PHY_PORT_NUM; loop++)
    {
        if(g_phy_port_info[loop].bus == 0xff)
        {
            continue;
        }
        gport = g_phy_port_info[loop].port_id;
        ret = cal_set_phy_prop(g_phy_port_info[loop].lchip, gport, CTC_PORT_PROP_PHY_CUSTOM_BASE+PHY_CUSTOM_POSTINIT, &value);
    }
    return ret==CTC_E_NOT_SUPPORT?CTC_E_NONE:ret;
}

/**
@fn int32 ctc_app_phy_device_init(uint8 lchip)
@ingroup SDK_API
@details This function do device initialize base on phy driver define.
@return error code.
*/
int32
ctc_app_phy_device_init(uint8 lchip, uint8 mode)
{
    int32 ret = 0;
    uint32 loop = 0;
    uint32 gport = 0;
    for (loop = 0; loop < CTC_MAX_PHY_PORT_NUM; loop++)
    { 
        if(g_phy_port_info[loop].bus == 0xff)
        {
            continue;
        }

        gport = g_phy_port_info[loop].port_id;
        if(mode == CTC_WARMBOOT)
        {
            ret = cal_set_phy_prop(g_phy_port_info[loop].lchip, gport, CTC_PORT_PROP_PHY_CUSTOM_BASE + PHY_WARMBOOT_SYNC, &loop);
        }
        else
        {
            ret = cal_set_port_prop(g_phy_port_info[loop].lchip, gport, CTC_PORT_PROP_PHY_INIT, 1);
        }
        if(ret)
        {
            PHY_LOG_ERR("Phy Init failed for gport %d !, ret = %d!\n", gport,ret);
        }
    }
    return ret==CTC_E_NOT_SUPPORT?CTC_E_NONE:ret;
}

/**
@fn int32 phy_init(uint8 load_mode,const char *pathname)
@ingroup SDK_API
@details This is the top-level api for phy device. Before this function call, we need done mdio module init.
    In this function, we do things as below:
    * phy driver register by call ctc_app_phy_register
    * phy info initialize by call ctc_app_phy_info_init
    * phy init

@param load_mode only use for AquantiaPHY, define for load firmware through mdio or flash
@param pathname firmware file name, only use for AquantiaPHY when load firmware through mdio
*/
int32
phy_init(uint8 mode,const char *pathname)
{
    int32 ret = 0;
    uint8 mapping_en[CTC_MAX_CHIP_NUM] = {0};
    uint8 drv_register_flag[CTC_MAX_CHIP_NUM] = {0};
    uint8 lchip_num = 0;
    uint8 chip_id;

    ret = cal_get_lchip_num(0, &lchip_num);
    ret = ctc_app_phy_info_init();
    if (ret != 0)
    {
        PHY_LOG_ERR("Phy info initialize failed! Maybe phy_mapping.cfg file is not exist, ret = %d!\n", ret);
    }

    for (chip_id = 0; chip_id < lchip_num; chip_id++)
    {
        ctc_app_get_phy_mdio_en(chip_id,&mapping_en[chip_id]);
        if(!mapping_en[chip_id])
        {
            PHY_LOG_INFO("Lchip %d Phy Driver Disable\n",chip_id);
            drv_register_flag[chip_id] = 0;
            continue;
        }
        drv_register_flag[chip_id] = 1;
        PHY_LOG_INFO("Lchip %d Phy Driver Enable\n",chip_id);
        ret = ctc_app_phy_register(chip_id);
        if (ret != 0)
        {
            PHY_LOG_ERR("Lchip %d Phy driver register failed! ret = %d!\n", chip_id, ret);
            drv_register_flag[chip_id] = 2;
            continue;
        }
        PHY_LOG_INFO("Lchip %d Phy Driver register success\n",chip_id);
    }

    for (chip_id = 0; chip_id < lchip_num; chip_id++)
    {
        if(drv_register_flag[chip_id] != 1)
        {
            /* for phy driver disable or phy driver register fail */
            return 0;
        }
    }
    
    ret += ctc_app_phy_device_pre_init(0);
    ret += ctc_app_phy_device_init(0, mode);
    ret += ctc_app_phy_device_post_init(0);
    return 0;
}

#endif
